typescript

[TS] 9. unknown Type

5 min read|20. 2. 21.

typescript_banner

TypeScript 3.0 version부터 unknown이라는 새로운 타입이 추가됐다.

any vs unknown

unknown 타입이 도입된 배경을 보다 잘 이해하기 위해 any 타입을 살펴봐야 한다. TypeScript에서 any는 모든 타입을 할당받을 수 있는 타입이다. 즉 any 타입으로 선언된 변수, argument는 모든 타입의 값이 할당될 수 있고 전달될 수 있는 것이다.

unknown 타입도 any과 마찬가지로 모든 타입의 값이 할당될 수 있다.

let variable: unknown

variable = true // OK (boolean)
variable = 1 // OK (number)
variable = 'string' // OK (string)
variable = {} // OK (object)

하지만 조금 다른 것은 unknown 타입으로 선언된 변수는 any를 제외한 다른 타입으로 선언된 변수에 할당될 수 없다는 것이다.

let variable: unknown

let anyType: any = variable
let booleanType: boolean = variable
// Error: Type 'unknown' is not assignable to type 'boolean'.(2322)
let numberType: number = variable
//  Error: Type 'unknown' is not assignable to type 'number'.(2322)
let stringType: string = variable
//  Error: Type 'unknown' is not assignable to type 'string'.(2322)
let objectType: object = variable
//  Error: Type 'unknown' is not assignable to type 'object'.(2322)

unknown 타입의 특징은 한 가지 더 있는데, unknown 타입으로 선언된 변수는 프로퍼티에 접근할 수 없으며, 메소드를 호출할 수 없으며, 인스턴스를 생성할 수도 없다. 알려지지 않은 타입이라 그런 것이다.

let variable: unknown

variable.foo.bar // Error: Object is of type 'unknown'.(2571)
variable[0] // Error
variable.trigger() // Error
variable() // Error
new variable() // Error

다음과 같이 Type Guard와 함께라면 가능하다. (https://jbee.io/articles/typescript-tutorials/TS-7-TypeScript-type-system/#type-guards)

let variable: unknown
declare function isFunction(x: unknown): x is Function

if (isFunction(variable)) {
  variable() // OK
}

물론 Type Assertion을 통해서 에러를 사라지게 할 수 있다. (Runtime에서 앱이 죽는 것을 바란다면 이것 또한 좋은 방법이다.) https://jbee.io/articles/typescript-tutorials/TS-7-TypeScript-type-system/#type-assertion

type_diagram

유니온(Union)은 쉽게 말하면 합집합이다. 따라서 unknown 타입과 다른 타입을 |로 유니온 타입으로 합성하게 되면 unknown 타입이 반환된다.

인터섹션(intersection)은 교집합이라고 할 수 있다. 따라서 unknown 타입과 다른 타입을 &로 인터섹션 타입으로 반환하게 되면 대상이 된 타입이 반환된다.

type unknownType = unknown | string // unknown
type stringType = unknown & string // string

unknown 타입에 대해 좀 더 깊게 알고 싶은 분들은 서재원님이 작성하신 안전한 any 타입 만들기 글을 읽어보면 좋을 것 같다.

When

그렇다면 unknown 타입을 언제 사용할 수 있을까?

Instead of any

any가 사용될 곳이라면 unknown 타입으로 대체할 수 있다. 위 예제 코드에서도 살펴봤듯이 unknown 타입으로 지정된 값은 타입을 먼저 확인 후에 무언가를 할 수 있기 때문에 안전하다.

Type Guard

isOfType이라는 어떤 객체의 프로퍼티로 타입을 판단하는 util function에 unknown type을 사용할 수 있다.

const isOfType = <T>(
  varToBeChecked: unknown,
  propertyToCheckFor: keyof T
): varToBeChecked is T =>
  (varToBeChecked as T)[propertyToCheckFor] !== undefined

varToBeChecked 인자로 어떤 타입의 값이 전달될지 알 수 없으므로 무한 유니온 타입(string | number | boolean | ...)으로 선언하던가 any 타입으로 선언해야 할텐데, 이 때 unknown 타입을 사용할 수 있다. 위 type guard util function은 다음과 같이 사용할 수 있다.

interface SomethingType {
  foo: string
  bar: number
  zoo: boolean
}

const anything = {
  foo: '',
}

console.log(isOfType<SomethingType>(anything, 'foo')) // true

마무리

any를 조금이라도 덜 쓰기 위해 tsc에 strict: true을 주고 컴파일 단계에서 발생할 수 있는 버그를 하나 둘씩 해결해 나가길 바란다.

References